WebWork uses DWR to implement its ajax validation feature.

To have DWR configured, the following snippet needs to be in web.xml

<servlet>
    <servlet-name>dwr</servlet-name>
    <servlet-class>uk.ltd.getahead.dwr.DWRServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>true</param-value>
    </init-param>
</servlet>

<servlet-mapping>
    <servlet-name>dwr</servlet-name>
    <url-pattern>/dwr/*</url-pattern>
</servlet-mapping>

and dwr.xml under WEB-INF should contains snippet like

<!DOCTYPE dwr PUBLIC 
	"-//GetAhead Limited//DTD Direct Web Remoting 1.0//EN" 
	"http://www.getahead.ltd.uk/dwr/dwr10.dtd">

<dwr>
    <allow>
        <create creator="new" javascript="validator">
            <param name="class" value="com.opensymphony.webwork.validators.DWRValidator"/>
        </create>
        <convert converter="bean" match="com.opensymphony.xwork.ValidationAwareSupport"/>
    </allow>

    <signatures>
        <![CDATA[
        import java.util.Map;
        import com.opensymphony.webwork.validators.DWRValidator;

        DWRValidator.doPost(String, String, Map<String, String>);
        ]]>
    </signatures>
</dwr>

With the above configuration, WebWork would be able to call

validator.doPost(
        function(validationAwareSupportReturned) { 
           ... 
        }, 
        action, namespace, params);

to invoke WebWork's DWRValidator's doPost(action, namespace, params) and processed the ValidationAwareSupport returned.

When there's a change on webwork ui components, WebWork will make such an ajax call, (as shown in /template/ajax/validation.js)

*
 * An "Ajax" theme implementation of ValidationClient, it provides function 
 * hook that client (eg. other theme like xhtml or css_html should implement
 * to customize handling of validation error to suit their needs, eg in an 
 * xhtml theme, it would maybe change the &lt;tr&gt; or &lt;td&gt; to show
 * or hide the validation messages etc.
 * 
 * The hooks are :-
 * <pre>
 * function  clearErrorMessage(form) {
 *    .....
 * }
 *
 * function clearErrorLabels(form) {
 *    ....
 * }
 *
 * function addError(fieldElement, fieldError) {
 *   ....
 * }
 * </pre>
 */
var webworkValidator = new ValidationClient("$!base/validation");
webworkValidator.onErrors = function(input, errors) {
	var form = input.form;

	clearErrorMessages(form);
	clearErrorLabels(form);

    if (errors.fieldErrors) {
        for (var fieldName in errors.fieldErrors) {
        	if (form.elements[fieldName]) {
            	if (form.elements[fieldName].touched) {
                	for (var i = 0; i < errors.fieldErrors[fieldName].length; i++) {
                    	addError(form.elements[fieldName], errors.fieldErrors[fieldName][i]);
                	}
            	}
            }
        }
    }
}

/*
 * Function that gets called when there's change on WebWork UI Components, 
 * eg. js onchange. It delegate the functionality of validating the changed 
 * components to an instance of ValidationClient (in this implementation 
 * webworkValidator).
 */
function validate(element) {
    // mark the element as touch
    element.touched = true;
    var namespace = element.form.attributes['namespace'].nodeValue;
    var actionName = element.form.attributes['name'].nodeValue;
	webworkValidator.validate(element, namespace, actionName);
}

/*

A new instance of ValidationClient js function is created for each form and the internal of contacting dwr is done there as well. Bellow is snippet of validationClient.js located in /static/validationClient.js

*
 * Common code to interface with the WebWork's ajax validation. The following
 * method is expected to be implemented by client to handle validation error
 * suited to their needs
 *
 * eg.
 * <pre>
 *     var validationClientInstance = new ValidationClient(...);
 *  
 *  / * 
 *    *  @param inputElement - the form object that triggered the validate call
 *	   *  @param errors - a javascript object representing the action errors and field errors
 *	   *                             client should overwrite this handler to display the new error messages
 *    * /
 *     validationClientInstance.onErrors = function(inputElement, errors) { 
 *        .....
 *     }
 * </pre>    
 *
 */
function ValidationClient(servletUrl) {

	this.servletUrl = servletUrl;

	this.validate = function(input, namespace, actionName) {
		var vc = this;
		var form = input.form;
		var params = new Object();
	    for (var i = 0; i < form.elements.length; i++) {
	        var e = form.elements[i];
            if (e.name != null && e.name != '') {
                params[e.name] = e.value;
            }
        }

		validator.doPost(function(action) {
            if (action) {
                vc.onErrors(input, action);
            }
        }, namespace, actionName, params);
    }
    
	return this;
}

/*

In ValidationClient.js function there's an empty implementation of onErrors, this is where codes of update the ui when there's an error etc. WebWork's ajax's theme actually overrides this with custom hooks as shown in /template/ajax/validation.js. Therefore a particular theme would need to implement the following functions to suite their own needs.

/**
  * Clear error message from the <code>form</code>
  */
 function clearErrorMessages(form)() { 
   ...
 }

 /*
  * Clear the error label from <code>form</code>, eg. change the style so they don't appeared as red in color
  */
 function clearErrorLabels(form) {

 }

 /*
  * Add error <code>errorOfFieldElement</code> to <code>fieldElement</code>.
  */
 function addError(fieldElement, errorOfFieldElement) {

 }

An example of xhtml's implementation of the above functions is as follows:-

*/
 
/*
 * This function is copied over from Dojo, such that 'xhtml' theme doesn't depends
 * on Dojo.
 */
function previousElement(/* Node */ node, /*string? */ tagName) { 
	//	summary:
	//		returns the previous sibling element matching tagName
	if(!node) { return null; }
	if(tagName) { tagName = tagName.toLowerCase(); }
	do {
		node = node.previousSibling;
	} while(node && node.nodeType != 1 /* ELEMENT_NODE */);

	if(node && tagName && tagName.toLowerCase() != node.tagName.toLowerCase()) {
		return previousElement(node, tagName);
	}
	return node;	//	Element
} 
 
function clearErrorMessages(form) {
    var table = form.childNodes[1];
    if( typeof table == "undefined" ) {
        table = form.childNodes[0];
    }

    // clear out any rows with an "errorFor" attribute
    var rows = table.rows;
    var rowsToDelete = new Array();
    if (rows == null){
        return;
    }

    for(var i = 0; i < rows.length; i++) {
        var r = rows[i];
        if (r.getAttribute("errorFor")) {
            rowsToDelete.push(r);
        }
    }

    // now delete the rows
    for (var i = 0; i < rowsToDelete.length; i++) {
        var r = rowsToDelete[i];
        table.deleteRow(r.rowIndex);
    }
}

function clearErrorLabels(form) {
    // set all labels back to the normal class
    var elements = form.elements;
    for (var i = 0; i < elements.length; i++) {
        var e = elements[i];
        
        var label;
        var cells = e.parentNode.parentNode.cells;
        if (cells && cells.length >= 2) {  // when labelposition='left'
        	label = cells[0].getElementsByTagName("label")[0];
        }
        else if (cells && cells.length >=1) { // when labelposition='top'
        	if (e.parentNode.parentNode) {
        		if (previousElement(e.parentNode.parentNode)) {      
        			label = previousElement(e.parentNode.parentNode).getElementsByTagName("label")[0];
        		}
        	}
        }	
        
        if (label) {
        	label.setAttribute("class", "label");
        	label.setAttribute("className", "label"); //ie hack cause ie does not support setAttribute
        }
    }
}

function addError(e, errorText) {
    try {
        // clear out any rows with an "errorFor" of e.id
        
        var row = e.parentNode.parentNode;
        var table = row.parentNode;
        var error = document.createTextNode(errorText);
        var tr = document.createElement("tr");
        var td = document.createElement("td");
        var span = document.createElement("span");
        td.align = "center";
        td.valign = "top";
        span.setAttribute("class", "errorMessage");
        span.setAttribute("className", "errorMessage"); //ie hack cause ie does not support setAttribute
        span.appendChild(error);
        td.appendChild(span);
        tr.appendChild(td);
        tr.setAttribute("errorFor", e.id);;

        // updat the label too
        var label;
        var cells = e.parentNode.parentNode.cells;
        if (cells && cells.length >= 2) { // when labelposition='left'
        	label = cells[0].getElementsByTagName("label")[0];
        	 td.colSpan = 2;
        }
        else {
        	if (previousElement(row)) { // when labelposition='top'
        		label = previousElement(row).getElementsByTagName("label")[0];
        	}
        }	
        
        if (label) {
        	label.setAttribute("class", "errorLabel");
        	label.setAttribute("className", "errorLabel"); //ie hack cause ie does not support setAttribute
        }
        
        table.insertBefore(tr, row);
    } catch (e) {
        alert(e);
    }
}

/*